package vg.interfaces.ll_services.data_base_service.data.graph;

import com.google.common.collect.Lists;

import java.util.*;

/**
 * This class is bean for data of graph.
 * Warning: No thread safety.
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class Graph extends AttributeItem implements Cloneable {
	// Constants
    private static final int ANY_NEIGHBORHOOD = 0;
    private static final int SRC_NEIGHBORHOOD = 1;
    private static final int TRG_NEIGHBORHOOD = 2;

	// Main data
	private Collection<Vertex> vertices;
	private Collection<Edge> edges;
	private boolean directed;
    private String name;

//=============================================================================
//-----------------Constructors------------------------------------------------
	public Graph(boolean directed) {
		this(new ArrayList<Vertex>(), new ArrayList<Edge>(), directed);
	}

	public Graph(Collection<Vertex> vertices, Collection<Edge> edges, boolean directed) {
		this(vertices, edges, directed, null);
	}

	public Graph(Collection<Vertex> vertices, Collection<Edge> edges, boolean directed, AttributeItem item) {
		super(item);
		if (vertices == null)
            this.vertices = Lists.newArrayList();
        else
            this.vertices = vertices;

        if (edges == null)
            this.edges = Lists.newArrayList();
        else
            this.edges = edges;
		this.directed = directed;
	}
	
//=============================================================================
//-----------------PUBLIC METHODS----------------------------------------------	
	public Vertex addVertex() {
		Vertex v = new Vertex();
		vertices.add(v);
		return v;
	}

	public boolean addVertex(Vertex v) {
		return vertices.add(v);
	}
	
	public Edge addEdge() {
		Edge e = new Edge();
		edges.add(e);
		return e;
	}

	public boolean addEdge(Edge e) {
		return edges.add(e);
	}
	
	public boolean removeVertex(Vertex v) {
		return vertices.remove(v);
	}
	
	public boolean removeEdge(Edge e) {
		return edges.remove(e);
	}
	
	public void setDirected(boolean directed) {
		this.directed = directed;
	}

	public boolean isDirected() {
		return directed;
	}
	
	public boolean isEmpty() {
		return vertices.isEmpty() && edges.isEmpty();
	}
	
	public Collection<Vertex> getVertices() {
		return vertices;
	}
	
	public Collection<Edge> getEdges() {
		return edges;
	}

    public int getVertexCount() {
        return vertices.size();
    }

    public int getEdgeCount() {
        return edges.size();
    }

    public Collection<Vertex> getSrcNeighborhoods(Vertex vertex) {
        return getNeighborhoods(vertex, SRC_NEIGHBORHOOD);
    }

    public Collection<Vertex> getTrgNeighborhoods(Vertex vertex) {
        return getNeighborhoods(vertex, TRG_NEIGHBORHOOD);
    }

    public Collection<Vertex> getAnyNeighborhoods(Vertex vertex) {
        return getNeighborhoods(vertex, ANY_NEIGHBORHOOD);
    }

    public int getIncomingEdgeCount(Vertex vertex) {
        return getSrcNeighborhoods(vertex).size();
    }

    public int getOutgoingEdgeCount(Vertex vertex) {
        return getTrgNeighborhoods(vertex).size();
    }

    public int getEdgeCount(Vertex vertex) {
        return getAnyNeighborhoods(vertex).size();
    }

    @Override
	public Graph clone() {
		Map<Vertex, Vertex> new_vertices = new HashMap<Vertex, Vertex>();
		for (Vertex v : vertices) {
			new_vertices.put(v, v.clone());
		}
		
		Collection<Edge> new_edges = new ArrayList<Edge>();
		for (Edge e : edges) {
			Vertex src = e.getSource();
			Vertex trg = e.getTarget();
			if (src != null && trg != null) {
				Vertex src_new = new_vertices.get(src);
				Vertex trg_new = new_vertices.get(trg);
				if (src_new != null && trg_new != null)
					new_edges.add(new Edge(src_new, trg_new, ((AttributeItem)e).clone()));
			}
		}
		
		return new Graph(new_vertices.values(), new_edges, directed, super.clone());
	}

//==============================================================================
//------------------PRIVATE METHODS---------------------------------------------
    /**
     * Returns neighborhoods for the vertex.
     *
     * Note: doesn't return empty collection.
     */
    private Collection<Vertex> getNeighborhoods(Vertex vertex, int neighborhoodType) {
        if (vertex == null)
            return Collections.emptyList();

        List<Vertex> result = Lists.newArrayList();

        for (Edge edge : edges) {
            Vertex src = edge.getSource();
            Vertex trg = edge.getTarget();

            if (neighborhoodType == ANY_NEIGHBORHOOD || neighborhoodType == TRG_NEIGHBORHOOD) {
                if (vertex.hashCode() == src.hashCode() && vertex.equals(src)) {
                    result.add(trg);
                }
            }
            if (neighborhoodType == ANY_NEIGHBORHOOD || neighborhoodType == SRC_NEIGHBORHOOD) {
                if (vertex.hashCode() == trg.hashCode() && vertex.equals(trg)) {
                    result.add(src);
                }
            }
        }

        return result;
    }
}
